import type { FileControllerApi } from '../openapi';
import type {
  ChunkInfo,
  FileChunk,
  ProgressListenerDelegate,
  UploadFileInfo,
} from './types';

import { StringFormat } from './utils';

/**
 * 文件分片上传器
 */
export class FileChunkUploader {
  private static readonly FAIL_TIMEOUT = 60_000;
  private static readonly MAX_RETRY = 5;

  /**
   * 获取文件API实例
   */
  get getFileApi(): FileControllerApi {
    return this.fileApi;
  }
  private readonly fileApi: FileControllerApi;
  private readonly fileMd5: string;
  private readonly fileName: string;

  private readonly totalSize: number;

  constructor(
    fileApi: FileControllerApi,
    fileName: string,
    fileMd5: string,
    totalSize: number,
  ) {
    this.fileApi = fileApi;
    this.fileName = fileName;
    this.fileMd5 = fileMd5;
    this.totalSize = totalSize;
  }

  /**
   * 检查上传是否已存在
   */
  async checkUploadExists(): Promise<boolean> {
    const response = await this.fileApi.checkUploadExists(this.fileMd5);
    return response.data;
  }

  /**
   * 完成上传
   */
  async complete(): Promise<string> {
    const response = await this.fileApi.completeUpload(this.fileMd5);
    return response.data;
  }

  /**
   * 获取已上传的分片信息
   */
  async getAllChunks(): Promise<ChunkInfo[]> {
    const response = await this.fileApi.getAllChunks(this.fileMd5);
    return response.data.map((chunk) => ({
      chunkIndex: chunk.chunkIndex ?? -1,
      chunkMD5: chunk.chunkMD5 ?? '?',
      chunkOffset: chunk.chunkOffset ?? -1,
    }));
  }

  /**
   * 开始上传（创建上传记录）
   */
  async start(): Promise<void> {
    const fileUploadInfo: UploadFileInfo = {
      fileName: this.fileName,
      totalSize: this.totalSize,
    };

    await this.fileApi.newUpload(this.fileMd5, fileUploadInfo);
  }

  /**
   * 上传单个文件分片
   */
  async uploadFileChunk(
    chunk: FileChunk,
    onProgress?: ProgressListenerDelegate,
  ): Promise<boolean> {
    let retryCount = 0;
    const startTime = Date.now();

    while (retryCount < FileChunkUploader.MAX_RETRY) {
      try {
        // 检查内存使用情况
        if (chunk.chunkData.byteLength > 10 * 1024 * 1024) {
          // 10MB
          console.warn(
            `上传大分片 (${chunk.chunkData.byteLength / (1024 * 1024)}MB), 可能影响性能`,
          );
        }

        // 将ArrayBuffer转换为字符串（Base64编码）
        const chunkDataBase64 = await this.arrayBufferToBase64(chunk.chunkData);

        // 模拟进度回调
        if (onProgress) {
          onProgress(0, chunk.chunkData.byteLength);
        }

        const response = await this.fileApi.uploadChunkBase64(
          this.fileMd5,
          chunk.chunkMd5,
          chunk.chunkIndex,
          chunk.chunkOffset,
          chunkDataBase64,
        );

        const now = Date.now();
        console.log(
          `分片 [${chunk.chunkIndex}] 上传成功，起始位置：${StringFormat.formatSize(chunk.chunkOffset)}，大小：${StringFormat.formatSize(chunk.chunkData.byteLength)}，分片 MD5：${chunk.chunkMd5}，耗时：${now - startTime}ms 返回：${response.data}`,
        );

        // 完成时的进度回调
        if (onProgress) {
          onProgress(chunk.chunkData.byteLength, chunk.chunkData.byteLength);
        }

        return true;
      } catch (error) {
        retryCount++;
        console.error(`上传分片失败，重试次数: ${retryCount}, 错误信息:`, {
          chunkIndex: chunk.chunkIndex,
          error: error instanceof Error ? error.message : String(error),
          stack: error instanceof Error ? error.stack : undefined,
        });

        // 检查是否是致命错误（不应重试）
        if (
          error instanceof Error &&
          (error.message.includes('Base64编码失败') ||
            error.message.includes('内存不足') ||
            error.message.includes('out of memory'))
        ) {
          console.error('检测到致命错误，停止重试');
          break;
        }

        if (retryCount < FileChunkUploader.MAX_RETRY) {
          await this.delay(2000 * retryCount); // 指数退避
        }
      }
    }

    console.error(`上传分片${chunk.chunkIndex}失败，超过最大重试次数`);
    return false;
  }

  /**
   * ArrayBuffer转Base64 - 优化内存使用，支持大文件分片
   */
  private async arrayBufferToBase64(buffer: ArrayBuffer): Promise<string> {
    try {
      const bytes = new Uint8Array(buffer);

      // 根据数据大小选择不同的处理策略
      if (bytes.byteLength <= 1024 * 1024) {
        // 1MB以下直接处理
        const binary = Array.from(bytes, (byte) =>
          String.fromCodePoint(byte),
        ).join('');
        return btoa(binary);
      }

      // 对于大文件分片，使用分块处理以避免内存溢出
      const chunkSize = 8192; // 8KB 分块
      let binary = '';

      console.log(
        `处理大分片Base64编码，大小: ${(bytes.byteLength / (1024 * 1024)).toFixed(2)}MB`,
      );

      for (let i = 0; i < bytes.byteLength; i += chunkSize) {
        const chunk = bytes.slice(i, i + chunkSize);
        const chunkBinary = Array.from(chunk, (byte) =>
          String.fromCodePoint(byte),
        ).join('');
        binary += chunkBinary;

        // 每处理一定量的数据后，给浏览器一个机会进行垃圾回收
        if (i % (chunkSize * 100) === 0 && i > 0) {
          // 使用setTimeout(0)让出执行权，允许浏览器进行其他操作
          await new Promise((resolve) => setTimeout(resolve, 0));
        }
      }

      const result = btoa(binary);
      console.log(
        `Base64编码完成，原始大小: ${(bytes.byteLength / (1024 * 1024)).toFixed(2)}MB, 编码后大小: ${(result.length / (1024 * 1024)).toFixed(2)}MB`,
      );

      return result;
    } catch (error) {
      console.error('Base64编码失败:', {
        bufferSize: buffer.byteLength,
        error: error instanceof Error ? error.message : String(error),
      });
      throw new Error(
        `Base64编码失败: ${error instanceof Error ? error.message : '未知错误'}`,
      );
    }
  }

  /**
   * 延迟函数
   */
  private delay(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}
